#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <string.h>

#define NIT_SHORT_NAMES
#include <nit/socket.h>

#include "tr.h"
#include "tr-bind.h"

#define EOT 4

enum { ERROR, OK, QUIT };

pthread_mutex_t action = PTHREAD_MUTEX_INITIALIZER;
double sps;
int state = OK;

static void
change_state(int new_state)
{
	pthread_mutex_lock(&action);
	state = new_state;
	pthread_mutex_unlock(&action);
}

static void
updater_end(int new_state, T3_trad *trad)
{
	change_state(new_state);
	t3_trad_release(trad);
	pthread_exit(NULL);
}

static void
sps_update(T3_trad *trad)
{
	pthread_mutex_lock(&action);
	sps = *(double *) trad->buf;
	pthread_mutex_unlock(&action);
}

static double
get_sps(void)
{
	double tmp;

	pthread_mutex_lock(&action);
        tmp = state;
	pthread_mutex_unlock(&action);
	return tmp;

}

static int
get_state(void)
{
	int tmp;

	pthread_mutex_lock(&action);
        tmp = state;
	pthread_mutex_unlock(&action);
	return tmp;
}

static void *
updater(void *args)
{
	Nit_joint *daemon = args;
	T3_trad trad;

	t3_trad_init(&trad, sizeof(sps));

	while (1) {
		if (get_state() != OK) {
			t3_trad_release(&trad);
			pthread_exit(NULL);
		}

		switch (t3_tr(daemon, &trad)) {
		case NIT_JOIN_NONE:
			break;
		case NIT_JOIN_CLOSED:
			if (get_state() != ERROR)
				updater_end(QUIT, &trad);

			updater_end(ERROR, &trad);
		case NIT_JOIN_ERROR:
			updater_end(ERROR, &trad);
		case NIT_JOIN_OK:
			if (trad.msg != sizeof(sps))
			        updater_end(QUIT, &trad);

			sps_update(&trad);
			break;
		}
	}

	pthread_exit(NULL);
}

static int
term_set(void)
{
	struct termios settings;

	if (tcgetattr(STDIN_FILENO, &settings) < 0) {
		perror("tcsetattr");
		return 0;
	}

	/* turns off canonical mode so we don't go line by line */
	settings.c_lflag &= ~ICANON;

	/* blocks read until we have atleast one byte */
        settings.c_cc[VMIN] = 0;
	settings.c_cc[VTIME] = 0;

	if (tcsetattr(STDIN_FILENO, TCSANOW, &settings) < 0) {
		perror("tcsetattr");
		return 0;
	}

	return 1;
}

static int
read_utf8(uint32_t *val, size_t *len)
{
        char c;
	ssize_t read_val;
	ssize_t rest;

	read_val = read(STDIN_FILENO, val, 1);

	if (read_val < 0)
		return 0;

	/* no input */
	if (!read_val) {
		*len = 0;
		return 1;
	}

        c = *(char *) val;
	*len = 0;

	for (; c >> 7; ++*len, c = c << 1);

	/* ASCII */
	if (!*len) {
		*len = 1;
		return 1;
	}

	if (*len > 4)
		return 0;

	rest = *len - 1;

	if (read(STDIN_FILENO, ((char *) val) + 1, rest) < rest)
		return 0;

	return 1;
}

int
t3_tr_bind(const char *name)
{
	int run = 1;
	time_t timer;
	size_t size = 0;
	char buf[1024];
	pthread_t update_thread;
	Nit_joint *daemon = joint_connect(name);

	if (!daemon)
		return 0;

	if (!term_set())
		return 0;

	if (pthread_create(&update_thread, NULL, updater, daemon))
		return 0;

	time(&timer);

	while (run) {
		uint32_t val;
		size_t len;
		time_t now;

		if (!read_utf8(&val, &len)) {
			change_state(ERROR);
			break;
		}

		if (*(char *) &val == EOT) {
			change_state(QUIT);
			break;
		}

		memcpy(buf + size, &val, len);
		size += len;

		if (!size)
			continue;

		if (get_state() != OK)
			break;

		time(&now);

		if (difftime(now, timer) > get_sps()) {
			if (!joint_send(daemon, buf, size)) {
				change_state(ERROR);
				break;
			}

			time(&timer);
			size = 0;
		}
	}

	pthread_join(update_thread, NULL);
	joint_free(daemon);
	printf("\n");
	return get_state() == QUIT;
}
